using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Xml;
using FormsGenerator.DataAccess;

namespace FormsGenerator.Xml
{
    /*
	public struct ValidationExpression
	{
		private string regularExpression;
		private string language;
		public string RegularExpression 
		{
			get 
			{
				return regularExpression;
			}
			set
			{
				regularExpression = value;
			}
		}

		public string Language
		{
			get
			{
				return language;
			}
			set
			{
				language = value;
			}
		}
	}
	*/

    public struct Validation
    {
        public string ID { get; set; }

        public string Name { get; set; }

        public string Description { get; set; }

        public Hashtable RegularExpressions { get; set; }

        public string ErrorMessage { get; set; }
    }

    /// <summary>
    /// Summary description for XmlHandler.
    /// </summary>
    public class XmlHandler
    {
        public static XmlDocument GetFormElements()
        {
            string formelementsfile = ConfigurationManager.AppSettings.Get("File-formElements.xml");
            return ReadXmlFile(formelementsfile);
        }

        public static XmlDocument GetEmptyForm()
        {
            string formelementsfile = ConfigurationManager.AppSettings.Get("File-formempty.xml");
            return ReadXmlFile(formelementsfile);
        }

        public static XmlDocument GetForm(string strGUID)
        {
            DataTable dt = DBFactory.Instance.DBHandlerInstance().GetForm(strGUID);
            if (dt.Rows.Count == 1)
            {
                var xmlDoc = new XmlDocument();
                var xml = (string) dt.Rows[0]["XML"];
                if (xml.Length > 0)
                {
                    xmlDoc.LoadXml(xml);
                    AddProperties(dt, xmlDoc);
                    return xmlDoc;
                }
                throw new ApplicationException("The Form with GUID = " + strGUID + " has no content!");
            }
            throw new ApplicationException("The Form with GUID = " + strGUID + " was not found!");
        }

        public static void SaveForm(string strGuid, XmlDocument xmlDoc, string strUserId)
        {
            var cleanedDoc = new XmlDocument();
            cleanedDoc.LoadXml(xmlDoc.InnerXml);
            string formName = cleanedDoc.SelectSingleNode("/form/properties/name").InnerText.Trim();
            XmlElement root = cleanedDoc.DocumentElement;

            //FIX - 1.1 -> 1.2
            //Add version attribute if not exists.
            if (root != null)
            {
                XmlAttribute versionAttribute = root.GetAttributeNode("version");
                if (versionAttribute == null)
                {
                    versionAttribute = cleanedDoc.CreateAttribute("version");
                    root.Attributes.Append(versionAttribute);
                }
                versionAttribute.Value = "1.2";
            }


            if (root != null)
            {
                XmlNode propertiesNode = root.SelectSingleNode("./properties");
                if (propertiesNode != null)
                {
                    root.RemoveChild(propertiesNode);
                }
            }
            DBFactory.Instance.DBHandlerInstance().SaveForm(strGuid, formName, cleanedDoc, strUserId);
        }

        public static XmlDocument CreateForm(string formName, int numOfPages, string owner)
        {
            XmlDocument newForm = GetEmptyForm();
            if (numOfPages > 1)
            {
                XmlNode pageNode = newForm.SelectSingleNode("/form/page");
                XmlNode formNode = newForm.DocumentElement;
                //The new form already has a page as default
                for (int i = 1; i < numOfPages; i++)
                {
                    XmlNode newPage = pageNode.Clone();
                    if (formNode != null) formNode.InsertAfter(newPage, pageNode);
                }
            }

            DataTable dt = DBFactory.Instance.DBHandlerInstance().CreateForm(formName, newForm, owner);
            AddProperties(dt, newForm);
            return newForm;
        }

        public static XmlDocument ParseXml(string xmlstring)
        {
            var formElements = new XmlDocument();
            formElements.LoadXml(xmlstring);
            return formElements;
        }

        private static XmlDocument ReadXmlFile(string filename)
        {
            var formElements = new XmlDocument();
            var readXml = new XmlTextReader(filename);
            formElements.Load(readXml);
            readXml.Close();
            return formElements;
        }

        private static void AddProperties(DataTable dt, XmlDocument xmlDoc)
        {
            XmlNode root = xmlDoc.DocumentElement;

            //Remove old property node! Some old XML have this one.
            if (root != null)
            {
                XmlNode propertiesNode = root.SelectSingleNode("./Properties");
                if (propertiesNode != null)
                {
                    root.RemoveChild(propertiesNode);
                }
                //Add new property node
                propertiesNode = xmlDoc.CreateElement("properties");
                root.AppendChild(propertiesNode);
                XmlNode newNode;

                foreach (DataColumn column in dt.Columns)
                {
                    // Do not include the XML itself!
                    if (!column.ColumnName.ToLower().Equals("xml"))
                    {
                        newNode = xmlDoc.CreateElement(column.ColumnName.ToLower());
                        newNode.InnerText = "" + dt.Rows[0][column.ColumnName];
                        propertiesNode.AppendChild(newNode);
                    }
                }
            }
        }

        public static void AddPage(string strGuid, int offset, XmlDocument xmlDoc, string strUserId)
        {
            XmlDocument newForm = GetEmptyForm();
            XmlNode cleanPageNode = newForm.SelectSingleNode("/form/page");
            XmlNode newPageNode = xmlDoc.ImportNode(cleanPageNode, true);
            XmlNode formNode = xmlDoc.DocumentElement;
            //If not placing page at end			
            if (offset > 1)
            {
                XmlNode pageNode = xmlDoc.SelectSingleNode("/form/page[" + offset + "]");
                if (formNode != null) formNode.InsertBefore(newPageNode, pageNode);
            }
            else
            {
                XmlNode pageNode = xmlDoc.SelectSingleNode("/form/page[last()]");
                if (formNode != null) formNode.InsertAfter(newPageNode, pageNode);
            }
            SaveForm(strGuid, xmlDoc, strUserId);
        }

        public static void DeletePage(string strGuid, int pageNumber, XmlDocument xmlDoc, string strUserId)
        {
            XmlNode pageNode = pageNumber > 1 ? xmlDoc.SelectSingleNode("/form/page[" + pageNumber + "]") : xmlDoc.SelectSingleNode("/form/page");
            XmlNode formNode = pageNode.ParentNode;
            formNode.RemoveChild(pageNode);
            SaveForm(strGuid, xmlDoc, strUserId);
        }

        public static void AddRow(string strGuid, int offset, int pageNumber, XmlDocument xmlDoc, string strUserId)
        {
            XmlNode newRowNode = xmlDoc.CreateElement("row");
            XmlNode pageNode = pageNumber > 1 ? xmlDoc.SelectSingleNode("/form/page[" + pageNumber + "]") : xmlDoc.SelectSingleNode("/form/page");

            //If not placing row at end			
            if (offset >= 1)
            {
                XmlNode rowNode = pageNode.SelectSingleNode("./row[" + offset + "]");
                pageNode.InsertBefore(newRowNode, rowNode);
            }
            else
            {
                XmlNode rowNode = pageNode.SelectSingleNode("./row[last()]");
                pageNode.InsertAfter(newRowNode, rowNode);
            }
            SaveForm(strGuid, xmlDoc, strUserId);
        }

        public static void DeleteRow(string strGuid, int rowNr, int pageNumber, XmlDocument xmlDoc, string strUserId)
        {
            XmlNode pageNode = pageNumber > 1 ? xmlDoc.SelectSingleNode("/form/page[" + pageNumber + "]") : xmlDoc.SelectSingleNode("/form/page");
            XmlNode rowNode = pageNode.SelectSingleNode("./row[" + rowNr + "]");
            pageNode.RemoveChild(rowNode);
            SaveForm(strGuid, xmlDoc, strUserId);
        }

        public static XmlElement GetItem(XmlDocument xmlDoc, int page, string position)
        {
            int itemnumber;
            XmlElement theItem = null;
            string[] positionArray = position.Split(new[] {'_'});
            if (positionArray.Length == 3)
            {
                itemnumber = int.Parse(positionArray[2]);
            }
            else
            {
                throw new ApplicationException("Could not get item!(" + position +
                                               ") position is invalid. The format should be [row]_[column]_[itemnumber].");
            }

            XmlElement theCell = GetCell(xmlDoc, page, position);
            if (theCell != null)
            {
                theItem = (XmlElement) theCell.SelectSingleNode("./item[" + itemnumber + "]");
            }
            return theItem;
        }

        public static XmlElement GetCell(XmlDocument xmlDoc, int page, string position)
        {
            int row;
            int column;
            string[] positionArray = position.Split(new[] {'_'});
            if (positionArray.Length == 3)
            {
                row = int.Parse(positionArray[0]);
                column = int.Parse(positionArray[1]);
            }
            else
            {
                throw new ApplicationException("Could not get cell!(" + position +
                                               ") position is invalid. The format should be [row]_[column]_[itemnumber].");
            }
            XmlNode rowNode = xmlDoc.SelectSingleNode("/form/page[" + page + "]/row[" + row + "]");
            XmlNodeList cells = rowNode.ChildNodes;
            XmlElement theCell = null;
            int cellCounter = 1;
            for (int i = 1; i <= cells.Count; i++)
            {
                theCell = (XmlElement) cells[i - 1];
                if (cellCounter == column)
                {
                    return theCell;
                }
                // Add colspan for this cell which will be used for next cell
                string strColspan = theCell.GetAttribute("colspan");
                int colspan = strColspan.Length > 0 ? int.Parse(strColspan) : 1;
                cellCounter += colspan;
            }
            //If cell doesn't exists, add some extra cells
            for (; cellCounter <= column; cellCounter++)
            {
                theCell = xmlDoc.CreateElement("cell");
                rowNode.AppendChild(theCell);
            }
            return theCell;
        }

        public static XmlElement EditItem(XmlElement itemToEdit, XmlElement formsElementType, Hashtable attributes,
                                          string innerHtml, ICollection validations)
        {
            if (itemToEdit == null)
            {
                throw new ApplicationException("No item to edit");
            }
            //Replacing existing element with a new element.				
            XmlDocument xmlDoc = itemToEdit.OwnerDocument;

            var existingElement = (XmlElement) itemToEdit.SelectSingleNode("./element").FirstChild;
            XmlElement newElement = CreateNewElement(xmlDoc, formsElementType, attributes, innerHtml);
            AddValidations(itemToEdit, validations);
            var parentNode = existingElement.ParentNode as XmlElement;

            //New in 1.3, adding name attribute
            if (parentNode != null)
            {
                XmlAttribute nameAttribute = parentNode.GetAttributeNode("name");
                if (nameAttribute != null)
                {
                    parentNode.Attributes.Remove(nameAttribute);
                }

                nameAttribute = xmlDoc.CreateAttribute("name");
                nameAttribute.Value = formsElementType.Attributes["name"].Value;
                parentNode.Attributes.Append(nameAttribute);
            }
            //1.3

            //New in 1.5, adding isdynamic attribute
            if (parentNode != null)
            {
                XmlAttribute isDynamicAttribute = parentNode.GetAttributeNode("isdynamic");
                if (isDynamicAttribute != null)
                {
                    parentNode.RemoveAttributeNode(isDynamicAttribute);
                }

                AddIsDynamicAttribute(formsElementType, xmlDoc, parentNode, isDynamicAttribute);
            }
            //1.5

            if (parentNode != null)
            {
                parentNode.InsertAfter(newElement, existingElement);
                parentNode.RemoveChild(existingElement);
            }
            return newElement;
        }

        private static void AddIsDynamicAttribute(XmlElement formsElementType, XmlDocument xmlDoc,
                                                          XmlNode parentNode, XmlAttribute isDynamicAttribute)
        {
            if (isDynamicAttribute == null) throw new ArgumentNullException("isDynamicAttribute");
            string isDynamicAttributeValue = formsElementType.GetAttribute("isdynamic");
            if (!string.IsNullOrEmpty(isDynamicAttributeValue))
            {
                isDynamicAttribute = xmlDoc.CreateAttribute("isdynamic");
                isDynamicAttribute.Value = isDynamicAttributeValue;
                parentNode.Attributes.Append(isDynamicAttribute);
            }
            return;
        }

        public static XmlElement CreateItem(XmlDocument xmlDoc, int page, string position, XmlElement formsElementType,
                                            Hashtable attributes, string innerHtml, ICollection validations)
        {
            XmlElement theItem = GetItem(xmlDoc, page, position);
            //The item does not exists, Create new cell and preceeding cells...
            if (theItem == null)
            {
                int row;
                int column;
                string[] positionArray = position.Split(new[] {'_'});
                if (positionArray.Length == 3)
                {
                    row = int.Parse(positionArray[0]);
                    column = int.Parse(positionArray[1]);
                }
                else
                {
                    throw new ApplicationException("Could not get item!(" + position +
                                                   ") position is invalid. The format should be [row]_[column]_[itemnumber].");
                }

                XmlNode rowNode = xmlDoc.SelectSingleNode("/form/page[" + page + "]/row[" + row + "]");
                XmlNodeList cells = rowNode.ChildNodes;
                int cellCounter = 1;

                XmlElement cellElement = null;

                //Get existing cells
                for (int i = 1; i <= cells.Count; i++)
                {
                    cellElement = (XmlElement) cells[i - 1];
                    // If found cell to edit!
                    if (cellCounter == column)
                    {
                        break;
                    }

                    // Add colspan for this cell which will be used for next cell
                    string strColspan = cellElement.GetAttribute("colspan");
                    int colspan = strColspan.Length > 0 ? int.Parse(strColspan) : 1;
                    cellCounter += colspan;
                }

                //Adding new cells if they don't exists...
                for (; cellCounter < column; cellCounter++)
                {
                    cellElement = xmlDoc.CreateElement("cell");
                    rowNode.AppendChild(cellElement);
                }

                XmlElement newItem = xmlDoc.CreateElement("item");
                XmlElement newElementTag = xmlDoc.CreateElement("element");
                XmlElement newElement = CreateNewElement(xmlDoc, formsElementType, attributes, innerHtml);
                AddValidations(newItem, validations);
                if (cellElement != null)
                {
                    cellElement.AppendChild(newItem);
                }
                newItem.AppendChild(newElementTag);
                newElementTag.AppendChild(newElement);

                //New in 1.3
                XmlAttribute nameAttribute = xmlDoc.CreateAttribute("name");
                nameAttribute.Value = formsElementType.Attributes["name"].Value;
                newElementTag.Attributes.Append(nameAttribute);
                //1.3

                //New in 1.5
                //XmlAttribute isDynamicAttribute = null;
                //isDynamicAttribute = AddIsDynamicAttribute(formsElementType, xmlDoc, newElementTag, isDynamicAttribute);

                return newItem;
            }
            //TODO:else edit the current item. not implemented...
            return null;
        }

        public static void DeleteItem(XmlDocument xmlDoc, int page, string position, string strGuid, string strUserId)
        {
            XmlElement theItem = GetItem(xmlDoc, page, position);
            XmlNode cellElement = theItem.ParentNode;
            cellElement.RemoveChild(theItem);
            SaveForm(strGuid, xmlDoc, strUserId);
        }

        private static XmlElement CreateNewElement(XmlDocument xmlDoc, XmlElement formsElementType, IDictionary attributes,
                                                   string innerHtml)
        {
            string tagName = formsElementType.GetAttribute("tag").ToLower();
            XmlElement newElement = xmlDoc.CreateElement(tagName);

            if (tagName.Equals("input"))
            {
                string tagType = formsElementType.GetAttribute("type").ToLower();
                XmlAttribute newAttribute = xmlDoc.CreateAttribute("type");
                newAttribute.Value = tagType;
                newElement.Attributes.Append(newAttribute);
            }

            //Adding new attributes to 
            foreach (string attribute in attributes.Keys)
            {
                string elementValue = ((string) attributes[attribute]).Trim();
                //Do not add attributes without values
                if (!elementValue.Equals(""))
                {
                    XmlAttribute newAttribute = xmlDoc.CreateAttribute(attribute);
                    newAttribute.Value = elementValue;
                    newElement.Attributes.Append(newAttribute);
                }
            }


            if (innerHtml != null && !innerHtml.Equals(""))
            {
                newElement.InnerXml = innerHtml;
            }

            return newElement;
        }

        private static void AddValidations(XmlNode itemToAddTo, ICollection validations)
        {
            XmlDocument xmlDoc = itemToAddTo.OwnerDocument;
            var existingValidation = (XmlElement) itemToAddTo.SelectSingleNode("./validations");
            //First remove existing validation for this item
            if (existingValidation != null)
            {
                itemToAddTo.RemoveChild(existingValidation);
            }

            //If adding validations
            if (validations.Count > 0)
            {
                XmlElement newValidations = xmlDoc.CreateElement("validations");
                foreach (Validation validation in validations)
                {
                    XmlElement newValidation = xmlDoc.CreateElement("validation");
                    if (!(validation.ID == null || validation.ID.Equals("")))
                    {
                        XmlAttribute idAttribute = xmlDoc.CreateAttribute("id");
                        idAttribute.Value = validation.ID;
                        newValidation.Attributes.Append(idAttribute);
                    }
                    XmlAttribute errorMessageAttribute = xmlDoc.CreateAttribute("errormessage");
                    errorMessageAttribute.Value = validation.ErrorMessage;
                    newValidation.Attributes.Append(errorMessageAttribute);

                    if (!(validation.Name == null || validation.Name.Equals("")))
                    {
                        XmlAttribute nameAttribute = xmlDoc.CreateAttribute("name");
                        nameAttribute.Value = validation.Name;
                        newValidation.Attributes.Append(nameAttribute);
                    }

                    foreach (string language in validation.RegularExpressions.Keys)
                    {
                        XmlElement newExpression = xmlDoc.CreateElement("expression");
                        XmlAttribute languageAttribute = xmlDoc.CreateAttribute("language");
                        languageAttribute.Value = language;
                        newExpression.Attributes.Append(languageAttribute);

                        XmlCDataSection regularExpression =
                            xmlDoc.CreateCDataSection((string) validation.RegularExpressions[language]);
                        newExpression.AppendChild(regularExpression);
                        newValidation.AppendChild(newExpression);
                    }
                    newValidations.AppendChild(newValidation);
                }
                itemToAddTo.AppendChild(newValidations);
            }
        }

        public static void moveItem(XmlDocument xmlDoc, int page, string originalPosition, string newPosition,
                                    string strGuid, string strUserId)
        {
            XmlElement itemToBeMoved = GetItem(xmlDoc, page, originalPosition);
            XmlElement existingItemInNewPosition = GetItem(xmlDoc, page, newPosition);
            XmlNode cellElement;
            //If moving to an empty cell
            if (existingItemInNewPosition == null)
            {
                //if cell doesn't exists

                cellElement = GetCell(xmlDoc, page, newPosition);
                cellElement.AppendChild(itemToBeMoved);
            }
            else
            {
                cellElement = existingItemInNewPosition.ParentNode;
                cellElement.InsertBefore(itemToBeMoved, existingItemInNewPosition);
            }
            SaveForm(strGuid, xmlDoc, strUserId);
        }
    }
}